package com.dbms;

import com.dbms.catalog.Catalog;
import com.dbms.common.Column;
import com.dbms.common.RID;
import com.dbms.common.Schema;
import com.dbms.common.Tuple;
import com.dbms.common.Type;
import com.dbms.execution.execution.*;
import com.dbms.storage.BPlusTree;
import com.dbms.storage.BufferPoolManager;
import com.dbms.storage.DiskManager;
import com.dbms.transaction.LogManager;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 一个用于演示 B+ 树功能的独立测试程序。
 *
 * 测试目标：
 *  1. 动态展示 B+ 树的构建与节点分裂过程。
 *  2. 对比使用全表扫描与 B+ 树索引扫描的查询性能（采用高精度计时）。
 *
 * 演示步骤：
 *  - Part 1: B+树动态插入数据，演示叶子分裂、根节点分裂等情况。
 *  - Part 2: 创建用户表，插入大量数据，对比全表扫描与索引扫描查询的耗时。
 */
public class BPlusTreeTest {

    /**
     * 程序入口。
     * 顺序运行两部分：分裂演示 和 性能对比。
     */
    public static void main(String[] args) throws IOException {
        System.out.println("============== B+树功能演示开始 ==============");

        // 第一部分：演示 B+树的构建与分裂过程
        demonstrateTreeSplitting();

        System.out.println("\n\n============== 分裂演示结束 ==============");
        System.out.println("============== 即将开始性能对比 ==============\n\n");

        // 停顿 2 秒，方便观察输出
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        // 第二部分：对比全表扫描和索引扫描的性能
        demonstratePerformanceGain();

        System.out.println("\n============== B+树功能演示结束 ==============");
    }

    /**
     * Part 1: 演示 B+树的构建和分裂过程。
     *
     * 步骤：
     *  - 初始化 B+树（指定叶子/内部节点容量）。
     *  - 依次插入若干键，观察树的结构变化。
     *  - 演示叶子分裂、父节点扩展、根节点分裂等情况。
     */
    private static void demonstrateTreeSplitting() throws IOException {
        String dbFileName = "bplustree_split_test.db";
        String logFileName = "bplustree_split_test.log";
        int leafMaxSize = 4;      // 每个叶子节点的最大容量
        int internalMaxSize = 4;  // 每个内部节点的最大容量

        System.out.println("--- Part 1: B+树的动态构建与分裂 ---");
        System.out.println("    (叶子节点容量=" + leafMaxSize + ", 内部节点容量=" + internalMaxSize + ")");
        System.out.println("--------------------------------------------------");

        // 删除旧的测试文件，确保结果干净
        new File(dbFileName).delete();
        new File(logFileName).delete();

        // 初始化存储管理组件
        DiskManager diskManager = new DiskManager(dbFileName);
        LogManager logManager = new LogManager(logFileName);
        BufferPoolManager bpm = new BufferPoolManager(10, diskManager, logManager);

        // 创建 B+ 树
        BPlusTree tree = new BPlusTree(bpm, -1, leafMaxSize, internalMaxSize);

        // 插入前 3 个键（不会分裂）
        for (int i = 1; i <= 3; i++) {
            System.out.println("\n--> 操作: 插入键 " + i);
            tree.insert(i, new RID(i, 0));
            tree.printTree();
        }

        // 插入第 4 个键，触发叶子分裂
        System.out.println("\n--> 操作: 插入键 4 (预期: 叶子节点分裂，创建新的根节点)");
        tree.insert(4, new RID(4, 0));
        tree.printTree();

        // 插入第 5、6 个键
        System.out.println("\n--> 操作: 插入键 5 和 6");
        tree.insert(5, new RID(5, 0));
        tree.insert(6, new RID(6, 0));
        tree.printTree();

        // 插入第 7 个键，右侧叶子节点分裂，根节点扩展
        System.out.println("\n--> 操作: 插入键 7 (预期: 右侧叶子节点分裂，父节点(根)增加键)");
        tree.insert(7, new RID(7, 0));
        tree.printTree();

        // 插入第 8、9 个键
        System.out.println("\n--> 操作: 插入键 8 和 9");
        tree.insert(8, new RID(8, 0));
        tree.insert(9, new RID(9, 0));
        tree.printTree();

        // 插入第 10 个键，触发根节点分裂，树高度增加
        System.out.println("\n--> 操作: 插入键 10 (预期: 叶子节点分裂，父节点(根)已满，根节点分裂，树的高度增加)");
        tree.insert(10, new RID(10, 0));
        tree.printTree();
    }

    /**
     * Part 2: 对比全表扫描和索引扫描的性能。
     *
     * 步骤：
     *  1. 创建 users 表 (id, data)。
     *  2. 插入大量数据（20,000 条）。
     *  3. 使用全表扫描查询某个 id，计时。
     *  4. 使用索引扫描查询相同 id，计时。
     *  5. 输出两者耗时对比，展示索引性能优势。
     */
    private static void demonstratePerformanceGain() throws IOException {
        String dbFileName = "bplustree_perf_test.db";
        String logFileName = "bplustree_perf_test.log";
        int recordCount = 20000;   // 插入的总记录数
        int keyToFind = 17895;     // 要查找的目标键值

        System.out.println("--- Part 2: 索引查询性能提升对比 (高精度) ---");
        System.out.println("    (将向表中插入 " + recordCount + " 条记录，然后查找键 " + keyToFind + ")");
        System.out.println("--------------------------------------------------");

        // 删除旧文件，保证干净测试环境
        new File(dbFileName).delete();
        new File(logFileName).delete();

        // 初始化存储、缓冲池和目录管理
        DiskManager diskManager = new DiskManager(dbFileName);
        BufferPoolManager bpm = new BufferPoolManager(100, diskManager, new LogManager(logFileName));
        Catalog catalog = new Catalog(bpm, true);

        // 定义表模式：id (主键, INT)，data (字符串)
        Schema schema = new Schema(List.of(
                new Column("id", Type.INTEGER, true),
                new Column("data", Type.VARCHAR, 100)
        ));

        // 执行建表
        Executor createTableExec = new CreateTableExecutor(catalog, "users", schema);
        createTableExec.open();
        createTableExec.next();
        createTableExec.close();
        System.out.println("表 'users' 创建完毕。");

        // 插入大量数据
        System.out.print("正在插入 " + recordCount + " 条记录... ");
        Catalog.TableMetadata usersTable = catalog.getTable("users");
        for (int i = 1; i <= recordCount; i++) {
            Tuple tuple = new Tuple(List.of(i, "user_data_" + i), schema);
            Executor insertExec = new InsertExecutor(bpm, usersTable, Collections.singletonList(tuple));
            insertExec.open();
            insertExec.next();
            insertExec.close();
        }
        bpm.flushAllPages();
        System.out.println("完成！");

        // --- 测试 1: 全表扫描 + 过滤器 ---
        System.out.println("\n--> 测试1: 使用 [全表扫描 + 过滤器] 查找 id = " + keyToFind);
        long startTime = System.nanoTime();

        Executor seqScan = new SeqScanExecutor(bpm, catalog, "users");
        Predicate predicate = new Predicate(0, "=", keyToFind);  // 按 id = keyToFind 过滤
        Executor filter = new FilterExecutor(predicate, seqScan);

        filter.open();
        Tuple resultTuple = filter.next();
        filter.close();

        long endTime = System.nanoTime();
        long seqScanDurationNanos = endTime - startTime;

        System.out.println("    查询结果: " + (resultTuple != null ? resultTuple : "未找到"));
        System.out.printf("    耗时: %.3f 毫秒\n", seqScanDurationNanos / 1_000_000.0);

        // --- 测试 2: 索引扫描 ---
        System.out.println("\n--> 测试2: 使用 [B+树索引扫描] 查找 id = " + keyToFind);
        startTime = System.nanoTime();

        Executor indexScan = new IndexScanExecutor(bpm, usersTable, keyToFind);
        indexScan.open();
        resultTuple = indexScan.next();
        indexScan.close();

        endTime = System.nanoTime();
        long indexScanDurationNanos = endTime - startTime;

        System.out.println("    查询结果: " + (resultTuple != null ? resultTuple : "未找到"));
        System.out.printf("    耗时: %.3f 毫秒\n", indexScanDurationNanos / 1_000_000.0);

        // --- 性能总结 ---
        System.out.println("\n--- 性能对比总结 ---");
        System.out.printf("    全表扫描耗时:   %.3f ms\n", seqScanDurationNanos / 1_000_000.0);
        System.out.printf("    索引扫描耗时:   %.3f ms\n", indexScanDurationNanos / 1_000_000.0);

        // 避免除零错误，做相对性能对比
        if (indexScanDurationNanos > 0 && seqScanDurationNanos > indexScanDurationNanos) {
            System.out.printf("    性能提升: 全表扫描的速度是索引扫描的 %.2f 倍！\n",
                    (double) seqScanDurationNanos / indexScanDurationNanos);
        } else {
            System.out.println("    性能提升巨大，索引扫描耗时远小于全表扫描！");
        }
    }
}
